#pragma once
#ifndef _KH_ENGINE_WORLD_H
#define _KH_ENGINE_WORLD_H
#include <Kharlia/Engine/Common.h>
#include <Kharlia/Engine/Units.h>
#include <Kharlia/Engine/Net.h>
#include <map>

namespace kharlia { namespace engine {

class CLevel;
class CEngine;
class CActor;
class CWorld;
class CPlayer;
class APlayerController;

enum ENetMode {
    NM_STANDALONE,
    NM_DEDICATED_SERVER,
    NM_LISTEN_SERVER,
    NM_CLIENT
};

//bool WriteGUID(RN::BitStream* out, object& guid);
//bool ReadGUID(RN::BitStream* in, object&);

/// The game logic base class.
/// This class acts as a central location for the game logic in a world.
/// There is only one game instance per world.
/// The game object is checked when adding players to the world.
class KH_ENGINE_API CGame: public core::CObject {
    DECL_PICKLER_N(CGame, tuple);
    /// The current world
    TWeakPtr<CWorld> mWorld;
    /// Whether the player has paused the game
    bool mPaused;
    /// If the game has been initialized
    //bool mInited;
    /// The real time, which is always updated even when the game is paused
    double mRealTime;
    /// The game time, which is not updated when the game is paused
    double mGameTime;

    void InitImpl();
    void ExitImpl();

    KH_DECL_GC()

protected:
    //void SetWorld(TPtr<CWorld> const&);

public:
    CGame(PyObject* self, TPtr<CWorld> world);
    virtual ~CGame() {}

    /// Updates the time
    virtual void Tick(float deltaTime);

    /// Initialize the game by binding it to a world
    void Init() {
        call_method<void>(mSelf, "Init");
    }
    /// Exit the game and remove bindings to the world
    void Exit() {
        call_method<void>(mSelf, "Exit");
    }

    TPtr<CWorld> GetWorld() const;
    bool GetPaused() const { return mPaused; }
    //bool GetInited() const { return mInited; }
    double GetRealTime() const { return mRealTime; }
    double GetGameTime() const { return mGameTime; }

    friend void _InitWorldClasses();
    friend void _InitWorldBindings();
    friend struct PSCGame;
};

// TODO: Have world instance create a default level for rep info actors.

/// The game world.
/// This is the highest level container for the game world. A world can be made of
/// multiple levels with multiple actors. Each world, when the game is started, has
/// a game object which acts as a central hub for game logic.
/// The world is also the object that manages the network interface.
class KH_ENGINE_API CWorld: public core::CObject, public FNetHandler {
public:
    typedef std::vector<CLevel*> LevelPtrMap;

private:
    DECL_PICKLER_N(CWorld, tuple);
    /// The engine that contains this world.
    TWeakPtr<CEngine> mEngine;
    /// True if the world is being ticked.
    bool mTicking;
    /// True if the world has been initialized.
    bool mInited;
    /// True if this is a world base without any levels
    bool mIsUnbound;

    bool mGameStarted;
    /// The game object if a game has been started.
    TPtr<CGame> mGame;
    /// The game object's class.
    /// This must be set in order to start a game.
    object mGameClass;
    /// Levels by name.
    dict mLevels;
    /// Levels by their name's hash number.
    dict mLevelHashMap;
    /// Levels by GUID
    dict mLevelGUIDMap;
    /// Level pointers for quick access.
    LevelPtrMap mLevelPtrs;
    /// The default level for objects that don't belong anywhere else.
    /// This is created by default when the world is initialized. It should have no
    /// Geometry and merely be used for storage.
    TPtr<CLevel> mDefaultLevel;
    /// Players in the world
    list mPlayers;

    // Network
    FNetPeer* mNetPeer;
    rn::NetworkID mNetDesiredPCID;

    /// Function to create a new GUID for new levels.
    static object msCreateGUIDFunc;
    /// The GUID object's type for checking.
    static object msGUIDClass;
    /// The class to be used when creating levels.
    static object msLevelClass;

    void SetEngine(TPtr<CEngine> const&);
    void NetReferenceExisting();
    void AddLevelImpl(TPtr<CLevel> level, bool check);
    void RemoveLevelImpl(TPtr<CLevel> level);

    KH_DECL_GC()

protected:
    void ClearImpl();

public:
    CWorld(PyObject* self);
    virtual ~CWorld();

    void Clear();
    void Tick(float deltaTime);
    void AddedToEngine(TPtr<CEngine> const&);
    void RemovedFromEngine(TPtr<CEngine> const&);
    object GetPlayerClass(bool isLocal=false) {
        return call_method<object>(mSelf, "GetPlayerClass", isLocal);
    }
    TPtr<CActor> AddPlayer(TPtr<CPlayer> p, ENetRole::Type remoteRole=ENetRole::NONE);
    void RemovePlayer(TPtr<CPlayer> p);
    void ClearPlayers();
    object IterPlayers() const;
    TPtr<APlayerController> CreatePlayerActor(TPtr<CPlayer> player, ENetRole::Type remoteRole=ENetRole::NONE) {
        return call_method<TPtr<APlayerController>>(mSelf, "CreatePlayerActor", player, remoteRole);
    }

    //TPtr<CLevel> CreateLevel(object guid, unicode name, FSizeI const& size);
    //void DestroyLevel(TPtr<CLevel>);
    //void DestroyLevels();
    void AddLevel(TPtr<CLevel> level, bool check=true) {
        call_method<void>(mSelf, "AddLevel", level, check);
    }
    void RemoveLevel(TPtr<CLevel> level) {
        call_method<void>(mSelf, "RemoveLevel", level);
    }
    void ClearLevels(bool clear=false);

    /// Start the game by creating the game object.
    void StartGame();
    void StopGame();
    void OnActorCreated(TPtr<CActor> actor, TPtr<CLevel> level);
    void OnActorDestroyed(TPtr<CActor> actor, TPtr<CLevel> level);

    /// Retrieve a level in the world by name or hash.
    TPtr<CLevel> GetLevel(object name);
    ENetMode GetNetMode();

    // Physics
    void InitPhysics();
    void CleanupPhysics();

    // Networking
    //void NetSendDebugMessage(str msg);
    //void NetReference(rn::Replica3* actor);
    //void NetDereference(rn::Replica3* actor);
    void NetListen();
    //void NetConnect(str host, uint16 port);
    void NetConnected(FNetPeer* peer, rn::RakNetGUID guid, rn::SystemAddress addr);
    void NetShutdown();
    void NetSendActorRPC(bool reliable, TPtr<CActor> actor, str funcname, tuple args);
    void NetOnReceiveActorRPC(rn::BitStream* bs);
    TPtr<CActor> NetGetActor(rn::NetworkID id);
    virtual void OnClientConnected(rn::RakNetGUID guid, rn::SystemAddress addr);
    virtual void OnClientDisconnected(rn::RakNetGUID guid, rn::SystemAddress addr, bool lost);
    virtual void OnServerConnected(rn::RakNetGUID guid, rn::SystemAddress addr);
    virtual void OnServerDisconnected(rn::RakNetGUID guid, rn::SystemAddress addr, bool lost);
    virtual bool OnReceive(rn::Packet& packet, rn::MessageID id);
    
    TPtr<CEngine> GetEngine() const { return mEngine.Lock(); }
    KH_DEF_GET(bool, Inited)
    bool GetIsUnbound() const { return mIsUnbound; }
    void SetIsUnbound(bool value) { mIsUnbound = value; }
    KH_DEF_GET(TPtr<CGame> const&, Game)
    void SetGame(TPtr<CGame> const& game);
    KH_DEF_GETSET(object const&, GameClass)
    KH_DEF_GET(dict const&, Levels)
    KH_DEF_GET(dict const&, LevelHashMap)
    KH_DEF_GET(LevelPtrMap const&, LevelPtrs)
    KH_DEF_GET(TPtr<CLevel> const&, DefaultLevel)
    bool GetGameStarted() const;
    FNetPeer* GetNetPeer() const { return mNetPeer; }

    static object CreateGUID();
    static void SetLevelClass(object cls);
    static object const& GetGUIDClass() { return msGUIDClass; }

    friend void _InitWorldClasses();
    friend void _InitWorldBindings();
    friend void _ExitWorldClasses();
    friend class CEngine;
    friend class CLevel;
    friend struct PSCWorld;
};

inline void CWorld::SetGame(TPtr<CGame> const& game) {
    mGame = game;
}

inline bool CWorld::GetGameStarted() const {
    return mGameStarted;
}

//inline void CWorld::NetReference(rn::Replica3* actor) {
//    if (mNetReplicaManager)
//        mNetReplicaManager->Reference(actor);
//}
//
//inline void CWorld::NetDereference(rn::Replica3* actor) {
//    if (mNetReplicaManager)
//        mNetReplicaManager->Dereference(actor);
//}

}} // namespace Kharlia

ENABLE_BACK_REFERENCE(kharlia::engine::CGame)
ENABLE_BACK_REFERENCE(kharlia::engine::CWorld)

#endif